﻿using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;
using System.Globalization;

namespace Citi.Import.Util
{
    public class Error
    {
        public int Line { get; set; }
        public List<string> Fields { get; set; }
        public string ErrorDescription { get; set; }
    }

    public enum TypeConversion
    {
        Both,
        Read,
        Write
    }

    public class Definition<T>
    {
        public int Position { get; set; }
        public CultureInfo Culture { get; set; }
        private Type Type { get; set; }
        internal PropertyInfo Property { get; set; }
        public int MaxLength { get; set; }
        public bool Required { get; set; }
        public string ColumnName { get; set; }
        public TypeConversion TypeConversion { get; set; }

        public CustomConversionMethodAction<T, string, List<string>, string, List<string>> CustomConversionMethod { get; set; }
        public delegate object CustomConversionMethodAction<T, U, V, W, X>(ref T model, U value, V fields, ref W error, X headers);

        private Expression<Func<T, object>> modelProperty;

        public Expression<Func<T, object>> ModelProperty
        {
            get { return modelProperty; }
            set
            {
                modelProperty = value;

                Property = Conversion.GetPropertyInfo(value);
                Type = Property.PropertyType;
            }
        }

        public object ConvertValue(ref T model, object valueToConvert, List<string> fields, List<string> headers,
                                   out String error, TypeConversion typeConversion)
        {
            error = null;
            var fieldName = string.IsNullOrEmpty(ColumnName) ? Property.Name : ColumnName;

            if (Required && (valueToConvert == null || string.IsNullOrEmpty(valueToConvert.ToString())))
            {
                error = string.Format("Field {0} is required.", fieldName);
                return null;
            }

            if (valueToConvert == null)
                return null;
            
            var value = valueToConvert.ToString();

            if (MaxLength > 0 && !string.IsNullOrEmpty(value) && value.Length > MaxLength)
            {
                error = string.Format("{0}: Maxlength is {1} but the field has {2} characters: '{3}'", fieldName, MaxLength, value.Length, value);
                return null;
            }

            if (typeConversion == TypeConversion.Read && CustomConversionMethod != null)
            {
                //StringBuilder errorStringBuilder = null;
                var customConverted = CustomConversionMethod(ref model, value, fields, ref error, headers);

                if (!string.IsNullOrEmpty(error))
                    return null;

                if (model == null) return null;

                if (Required && customConverted == null)
                {
                    error = string.Format("Field {0} is required.", fieldName);
                    return null;
                }

                return customConverted ?? Conversion.Validation.ManuallyValidated;
            }

            if (typeConversion == TypeConversion.Write && CustomConversionMethod != null)
            {

            }

            if (typeConversion == TypeConversion.Write)
                return value;

            if (Required || !string.IsNullOrEmpty(value))
            {
                var converted = ConvertType(value, Type, Culture, out error);

                return converted;
            }

            return null;
        }

        public object ConvertType(object value, Type conversionType, CultureInfo culture, out string error)
        {
            error = null;
            try
            {
                //return Convert.ChangeType(value, conversionType, culture);
                if (conversionType.IsGenericType && conversionType.GetGenericTypeDefinition() == typeof(Nullable<>))
                    return String.IsNullOrEmpty(value.ToString())
                               ? null
                               : Convert.ChangeType(value, conversionType.GetGenericArguments()[0], culture);
                return Convert.ChangeType(value, conversionType, culture);
            }
            catch (Exception)
            {
                error = string.Format("{0} The expected type is {1} but the content is '{2}'.", ColumnName, conversionType, value);
                return null;
            }
        }
    }
}
